home *** CD-ROM | disk | FTP | other *** search
- /*------------------------------------------------------------------------------
- Copyright (c) 2008 Ensolis, LLC. All Rights Reserved.
- ----------------------------------------------------------------------------*/
-
- /******************************************************************************
- * Get the selected forecastfox locale.
- *
- * @return The locale name.
- ******************************************************************************/
- function getLocale()
- {
- var reg = Cc["@mozilla.org/chrome/chrome-registry;1"].
- getService(Ci.nsIXULChromeRegistry);
- return reg.getSelectedLocale("forecastfox");
- }
-
- /******************************************************************************
- * Evaluate the data and return the correct data type.
- *
- * @param The data type.
- * @param The value.
- * @return The value in the correct javascript data type.
- ******************************************************************************/
- function evalData(aType, aData)
- {
- var rv = "";
- switch (aType) {
-
- //integer data type
- case "Int":
- rv = (aData == null) ? 0 : Number(aData);
- break;
-
- //boolean data type
- case "Bool":
- rv = (aData == null) ? false : Boolean(Number(aData));
- break;
-
- //string data type
- case "Char":
- default:
- rv = (aData == null) ? "" : String(aData);
- break;
- }
-
- //return the data
- return rv;
- }
-
- /******************************************************************************
- * Interfaces used by a service for parsing the weather feed. Supplies a set
- * items and methods used for manipulating the data. Targets are stored as a
- * flat list with an id made up of the target and the index with path and group
- * properties. Items are stored in a flat list in a group object. The id is
- * made up of the group and item name. The group property on the target
- * is used to retrieve the group object which has the items. Data is stored
- * in a flat list of target, index, and item name.
- *
- * @status FROZEN
- * @version 1.0
- ******************************************************************************/
- function ParserService()
- {
- //setup a new error
- this._error = Cc["@ensolis.com/forecastfox/error-item;1"].
- createInstance(Ci.ffIErrorItem);
- }
- ParserService.prototype = {
- __proto__: new ServiceBase("ParserService"),
- _dskSvc: null,
- _cnvSvc: null,
- _prfSvc: null,
- _file: null,
- _items: null,
- _data: null,
- _hasData: null,
- _resolver: null,
- _locale: null,
-
- ////////////////////////////////
- // ffIService
-
- /**
- * Initialize the component. Called by the manager service.
- */
- start: function ParserService_start()
- {
- //setup the variables
- this._items = {};
- this._data = {};
- this._hasData = false;
- this._locale = getLocale();
-
- //get the disk and conversion services
- var mgrSvc = Cc["@ensolis.com/forecastfox/manager-service;1"].
- getService(Ci.ffIManagerService);
- this._dskSvc = mgrSvc.disk;
- this._cnvSvc = mgrSvc.converters;
-
- //get the profile service so the [prof] variable can use it
- this._prfSvc = mgrSvc.profiles;
-
- //get the xpath resolver
- this._resolver = Cc["@ensolis.com/forecastfox/resolver-item;1"].
- createInstance(Ci.nsIDOMXPathNSResolver);
-
- //set the feed namespace
- if (this._resolver instanceof Ci.ffIItem)
- this._resolver.setProperty("adc", "http://www.accuweather.com");
-
- //get the parser file
- this._file = this._dskSvc.get("parser.js", TYPE_DEFAULTS);
-
- //load the data
- return this._loadParser();
- },
-
- /**
- * Destroy the component. Called by the manager service. This may be
- * called prior to start so it needs to be safe.
- */
- stop: function ParserService_stop()
- {
- //clear variables
- this._dskSvc = null;
- this._cnvSvc = null;
- this._prfSvc = null;
- this._file = null;
- this._items = null;
- this._data = null;
- this._hasData = null;
- this._resolver = null;
- this._locale = null;
- },
-
- ////////////////////////////////
- // ffIParserService
-
- /**
- * Last modified date of datasource.
- */
- get lastModified() {
- if (!this._file || !this._file.exists())
- return 0;
- else
- return this._file.lastModifiedTime;
- },
-
- /**
- * Parse the DOM document supplied by the feed. Returns false if
- * parsing failed. Use lastError attribute to obtain more detailed
- * error information.
- *
- * @param The DOM document to parse.
- * @return False if parsing failed.
- */
- parseDOM: function ParserService_parseDOM(aDocument)
- {
- //reset the data variable
- this._data = {};
- this._hasData = false;
-
- //setup error variables
- const PREFIX = "ff.parser.dom.";
- var name = "";
- var message = "";
-
- //check that the parser is loaded
- if (!this._items.hasOwnProperty("targets")) {
- name = this.bundle.GetStringFromName(PREFIX + "loaded.name");
- message = this.bundle.GetStringFromName(PREFIX + "loaded.message");
- this._error.init(SEVERITY_ERROR, name, message);
- return false;
- }
-
- //error if document is not well formed
- if (!this._dskSvc.validate(aDocument, "adc_database")) {
- name = this.bundle.GetStringFromName(PREFIX + "valid.name");
- message = this.bundle.GetStringFromName(PREFIX + "valid.message");
- this._error.init(SEVERITY_ERROR, name, message);
- return false;
- }
-
- //check for error messages
- var node = this._evalPath("./adc:failure", aDocument);
- if (node) {
- name = node.textContent;
- message = node.textContent;
- this._error.init(SEVERITY_ERROR, name, message);
- return false;
- }
-
- //loop through the targets
- for (var id in this._items.targets) {
- var target = this._items.targets[id];
-
- //get the items for the target and the context node
- var items = this._items.groups[target.group];
- node = this._evalPath(target.path, aDocument.documentElement);
-
- //loop through the items and evaluate
- for (var id2 in items) {
- var id3 = target.name + "-" + String(target.index) + "-" + id2;
- this._data[id3] = this._evalItem(items[id2], node);
- }
- }
-
- //parse was successful
- this._hasData = true;
- return true;
- },
-
- /**
- * Serialize the parsed document to javascript source. Used to cache
- * the parser items. Use the parseCache function to read the cache.
- * Returns false if serializing failed. Use lastError attribute
- * to obtain more detailed error information.
- *
- * @param The file to serialize to.
- * @return False if serialize failed.
- */
- serializeCache: function ParserService_serializeCache(aCache)
- {
- //setup error variables
- const PREFIX = "ff.parser.serialize.";
- var name = this.bundle.GetStringFromName(PREFIX + "name");
- var message = "";
-
- //no data stored
- if (!this._hasData) {
- message = this.bundle.GetStringFromName(PREFIX + "empty.message");
- this._error.init(SEVERITY_WARNING, name, message);
- return false;
- }
-
- //cache file is not writable
- if ((aCache.exists() && !aCache.isWritable()) ||
- (!aCache.exists() && !aCache.parent.isWritable())) {
- message = this.bundle.formatStringFromName(PREFIX + "write.message",
- [aCache.path], 1);
- this._error.init(SEVERITY_ERROR, name, message);
- return false;
- }
-
- //convert the items to javascript source
- var content = this._data.toSource();
-
- //write to the cache
- this._dskSvc.writeText(aCache, content, false, false, false);
- return true;
- },
-
- /**
- * Parse the cached parser items. Returns false if
- * parsing failed. Use lastError attribute to obtain more detailed
- * error information.
- *
- * @param The cache file.
- * @return False if parsing failed.
- */
- parseCache: function ParserService_parseCache(aCache)
- {
- //reset the data variable
- this._data = {};
- this._hasData = false;
-
- //setup error variables
- const PREFIX = "ff.parser.cache.";
- var name = "";
- var message = "";
-
- //check that the cache file exists
- if (!aCache.exists()) {
- name = this.bundle.GetStringFromName(PREFIX + "exists.name");
- message = this.bundle.GetStringFromName(PREFIX + "exists.message");
- this._error.init(SEVERITY_ERROR, name, message);
- return false;
- }
-
- //check that the cache file is readable
- if (!aCache.isReadable()) {
- name = this.bundle.GetStringFromName(PREFIX + "read.name");
- message = this.bundle.formatStringFromName(PREFIX + "read.message",
- [aCache.path], 1);
- this._error.init(SEVERITY_ERROR, name, message);
- return false;
- }
-
- //read the cache
- var content = this._dskSvc.readText(aCache);
- if (!content) {
- name = this.bundle.GetStringFromName(PREFIX + "content.name");
- message = this.bundle.GetStringFromName(PREFIX + "content.message");
- this._error.init(SEVERITY_ERROR, name, message);
- return false;
- }
-
- /** check that the cache is older than the parser. This needs to be
- done after reading the cache because there could have been
- outstanding file writes which the read will force to occur. **/
- if (this.lastModified > aCache.lastModifiedTime) {
- name = this.bundle.GetStringFromName(PREFIX + "last.name");
- message = this.bundle.GetStringFromName(PREFIX + "last.message");
- this._error.init(SEVERITY_ERROR, name, message);
- return false;
- }
-
- //set items
- try {
- this._data = eval(content);
- } catch(e) {
- this._data = {};
- this._error.init(SEVERITY_ERROR, e.name, e.message);
- return false;
- }
-
- //parse was successful
- this._hasData = true;
- return true;
- },
-
- /**
- * Parser has a specific item. Use the target, index, and item name
- * separated by hyphens as the id of the item.
- *
- * @param ID of the parser item.
- * @return True if item exists.
- */
- hasItem: function ParserService_hasItem(aID)
- {
- //split the id into its parts
- var unique = aID.split("-");
-
- //check that we have the target
- var id = unique[0] + "-" + unique[1];
- if (!this._items.targets.hasOwnProperty(id))
- return false;
-
- //check actual item
- var target = this._items.targets[id];
- var items = this._items.groups[target.group];
- if (items.hasOwnProperty(unique[2]))
- return true;
-
- //check global items
- target = this._items.targets["global-0"];
- items = this._items.groups[target.group];
- return items.hasOwnProperty(unique[2]);
- },
-
- /**
- * Retrieve a parser item returns null if item doesn't exist.
- * Use the target, index, and item name separated by hyphens
- * as the id of the item.
- *
- * @param ID of the parser item.
- * @return A ffIParserItem.
- */
- getItem: function ParserService_getItem(aID)
- {
- //split the id into its parts
- var unique = aID.split("-");
-
- //check that we have the target
- var id = unique[0] + "-" + unique[1];
- if (!this._items.targets.hasOwnProperty(id))
- return null;
-
- //check actual item
- var target = this._items.targets[id];
- var items = this._items.groups[target.group];
- if (items.hasOwnProperty(unique[2]))
- return this._createItem(items[unique[2]], target);
-
- //check global items
- target = this._items.targets["global-0"];
- items = this._items.groups[target.group];
- if (items.hasOwnProperty(unique[2]))
- return this._createItem(items[unique[2]], target);
-
- //item was not found
- return null;
- },
-
- /**
- * Retrieve an array of parser items.
- *
- * @param Target of the item.
- * @param Index of the item.
- * @param Count of items in the array.
- * @return An array of ffIParserItem components.
- */
- getItems: function ParserService_getItems(aTarget, aIndex, aCount)
- {
- //do globals first
- var rv = [];
- var target = this._items.targets["global-0"];
- var items = this._items.groups[target.group];
-
- //loop through the items
- for (var id in items) {
- var item = items[id];
-
- //skip hidden items
- if (item.hasOwnProperty("hidden"))
- continue;
-
- //add the items
- var id2 = target.name + "-" + String(target.index) + "-" + id;
- rv.push(this.getItem(id2));
- }
-
- //check the specific target
- id = aTarget + "-" + String(aIndex);
- if (!this._items.targets.hasOwnProperty(id) || aTarget == "global") {
- aCount.value = rv.length;
- return rv;
- }
-
- //get the target data
- target = this._items.targets[id];
- items = this._items.groups[target.group];
-
- //loop through the items
- for (id in items) {
- item = items[id];
-
- //skip hidden items
- if (item.hasOwnProperty("hidden"))
- continue;
-
- //add the items
- id2 = target.name + "-" + String(target.index) + "-" + id;
- rv.push(this.getItem(id2));
- }
-
- //return the array
- aCount.value = rv.length;
- return rv;
- },
-
- /**
- * Retrieve the value of a parser item. Pass in a
- * converter name to use in the conversion process
- * or null for the default value.
- *
- * @param Target of the item.
- * @param Index of the item.
- * @param Name of the item.
- * @param Name of the converter or null.
- * @return The formatted value of the item.
- */
- getValue: function ParserService_getValue(aTarget, aIndex, aName, aConverter)
- {
- var item = null;
-
- //check that we have the target
- var id = aTarget + "-" + String(aIndex);
- if (!this._items.targets.hasOwnProperty(id))
- return null;
-
- //check actual item
- var target = this._items.targets[id];
- var items = this._items.groups[target.group];
- if (items.hasOwnProperty(aName))
- item = items[aName];
- else {
-
- //check global items
- target = this._items.targets["global-0"];
- items = this._items.groups[target.group];
- if (items.hasOwnProperty(aName))
- item = items[aName];
- }
-
- //item was not found
- if (!item)
- return null;
-
- //return an N/A if data doesn't exist
- id = target.name + "-" + String(target.index) + "-" + item.name;
- if (!this._data.hasOwnProperty(id))
- return "N/A";
-
- //no data so return the value
- var value = this._data[id];
- if (value == "N/A")
- return value;
-
- /**
- * calculations are always ran first to get the correct
- * javascript context.
- */
- var comp = this;
- if (item.hasOwnProperty("calc")) {
- var calc = item.calc;
- calc = calc.replace(/\$VAL/g, "'" + String(value) + "'");
- try {
- value = eval(calc);
- } catch(e) {}
- }
-
- /** if we are being called recursively return early so we do not convert
- the same value multiple times. **/
- if (Components.stack.caller.name == "ParserService_getValue")
- return value;
-
- //use the conversion service
- if (item.hasOwnProperty("conversion"))
- value = this._cnvSvc.formatValue(item.conversion, aConverter, value);
-
- //return the final value
- return value;
- },
-
- /**
- * Builds a label from the passed in string. If the label has a "+" within
- * a parser name it will be split and the remaining will be used to retrieve
- * the converter to use for that value. This allows multiple uom to be
- * displayed for an item.
- *
- * @param The label string to format.
- * @param Target of the item or null.
- * @param Index of the item or null.
- * @return The formatted label.
- */
- getLabel: function ParserService_getLabel(aLabel, aTarget, aIndex)
- {
- //nothing has been parsed
- if (!this._hasData)
- return "";
-
- //function used to replace
- var comp = this;
- function getLabel_replace(aContent) {
-
- //get the string text
- var text = aContent.substring(1, aContent.length -1);
- text = text.split("+");
-
- //get the item and converter names
- var name = text[0];
- var converter = null;
- if (text.length > 1)
- converter = text[1];
-
- //create the item id and see if we have it
- var value = comp.getValue(aTarget, aIndex, name, converter);
- if (value != null)
- return value;
- else
- return text;
- }
-
- //do the replace
- var label = aLabel;
- try {
- label = aLabel.replace(/\[[^\[\]]+\]/g, getLabel_replace);
- } catch (e) {}
-
- //return the formatted label
- getLabel_replace = null;
- return label;
- },
-
- ////////////////////////////////
- // Internal Functions
-
- /**
- * Evaluate an xpath expression. Use the node as the context to
- * evaluate the expression in.
- *
- * @param Expression to evaluate.
- * @param Context node.
- * @return A DOM node result.
- */
- _evalPath: function ParserService__evalPath(aExpr, aNode)
- {
- //get the document
- var doc;
- try {
- doc = (aNode.ownerDocument == null) ? aNode : aNode.ownerDocument;
- } catch(e) {
- this._dskSvc.log("Error evaluating xpath.", e, null);
- return null;
- }
-
- //evaluate the expression
- var result = null;
- try {
- var results = doc.evaluate(aExpr, aNode, this._resolver,
- Ci.nsIDOMXPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
- null);
- result = results.snapshotItem(0);
- } catch(e) {}
-
- //return the variable
- return result;
- },
-
- /**
- * Evaluate an item to get the data from the feed.
- *
- * @param The item to evaluate.
- * @param The context node.
- * @return The evaluated data.
- */
- _evalItem: function ParserService__evalItem(aItem, aContext)
- {
- //get the data type
- var type = aItem.type;
-
- //no path so return empty data
- if (!aItem.hasOwnProperty("path"))
- return evalData(type, null);
- else
- var path = aItem.path;
-
- //process the prepath
- var node = null;
- if (aItem.hasOwnProperty("prepath")) {
- node = this._evalPath(aItem.prepath, aContext);
- if (!node)
- path = path.replace(/\$PRE/g, "''");
- else
- path = path.replace(/\$PRE/g, node.textContent);
- } else
- path = path.replace(/\$PRE/g, "''");
-
- //process the path
- node = this._evalPath(path, aContext);
- if (!node)
- return evalData(type, null);
-
- //return the value
- return (node.textContent == "N/A") ? "N/A" :
- evalData(type, node.textContent);
- },
-
- /**
- * Translate given feed data in to localized values.
- *
- * @param Target of the item to translate.
- * @param Index of the item to translate.
- * @param Name of the item to translate.
- * @param Converter name for getValue call.
- * @return The localized version of the feed data.
- */
- _translate: function ParserService__translate(aTarget, aIndex, aName, aConverter)
- {
- var value = null;
- const PREFIX = "ff.parser.";
-
- //translate forecast based on icon
- if (aName == "t" || aName == "tlong") {
-
- //if we are using an English locale, then just use the feed's data.
- if (this._locale.match("en-"))
- return this.getValue(aTarget, aIndex, aName + "_en", aConverter);
-
- //international version... so get the icon and then return the corresponding string.
- value = this.getValue(aTarget, aIndex, "icon", aConverter);
- return this.bundle.GetStringFromName(PREFIX + "forecast." + value);
- }
-
- ///translate all others based on the code value
- value = this.getValue(aTarget, aIndex, aName + "code", aConverter);
-
- //change to lower case and replace space with underscore
- value = value.toLowerCase().replace(/\//g, "");
- value = value.replace(/\s/g, "_");
-
- //get the translation
- try {
- value = this.bundle.GetStringFromName(PREFIX + aName + "." + value);
- } catch(e) {}
-
- //return the value
- return value;
- },
-
- /**
- * Create a parser item from a javascript object
- * representing that item.
- *
- * @param The javascript representation of the item.
- * @param The target of the item.
- * @return The parser item.
- */
- _createItem: function ParserService__createItem(aItem, aTarget)
- {
- //create an empty parser item
- var item = Cc["@ensolis.com/forecastfox/parser-item;1"].
- createInstance(Ci.ffIParserItem);
-
- //loop through the properties
- for (var property in aItem)
- item.setProperty(property, aItem[property]);
-
- //set the target based properties
- item.setProperty("target", aTarget.name);
- item.setProperty("index", aTarget.index);
- var id = item.target + "-" + String(item.index) + "-" + item.name;
- item.setProperty("ID", id);
-
- //get description
- var alias = item.getProperty("alias");
- alias = (alias != null) ? alias : item.name;
- var description = item.name;
- try {
- description = this.bundle.GetStringFromName("ff.parser." + alias);
- } catch(e) {}
- item.setProperty("description", description);
-
- //return the item
- return item;
- },
-
- /**
- * Load the parser data from file.
- *
- * @return False if the load fails.
- */
- _loadParser: function ParserService__loadParser()
- {
- //setup error variables
- const PREFIX = "ff.parser.load.";
- var name = this.bundle.GetStringFromName(PREFIX + "name");
- var message = "";
-
- //file doesn't exist
- if (!this._file.exists()) {
- message = this.bundle.GetStringFromName(PREFIX + "exists.message");
- this._error.init(SEVERITY_ERROR, name, message);
- return false;
- }
-
- //file is not readable
- if (!this._file.isReadable()) {
- message = this.bundle.formatStringFromName(PREFIX + "read.message",
- [this._file.path], 1);
- this._error.init(SEVERITY_ERROR, name, message);
- return false;
- }
-
- //get the content of the file
- var content = this._dskSvc.readText(this._file);
- if (!content) {
- message = this.bundle.GetStringFromName(PREFIX + "empty.message");
- this._error.init(SEVERITY_ERROR, name, message);
- return false;
- }
-
- //save the data in the items variable
- try {
- this._items = eval(content);
- } catch(e) {
- this._items = {};
- this._error.init(SEVERITY_ERROR, name, e.message);
- return false;
- }
-
- //loaded successfully
- return true;
- }
- };